// SPDX-License-Identifier: GPL-2.0
/*
 * INTERRUPT
 *
 * (C) 2024.05.20 BuddyZhang1 <buddy.zhang@aliyun.com>
 */
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/pci.h>

#define DEV_NAME		"BiscuitOS-DMA"
#define MMIO_BAR		0x02

/* DMA Register Maps */
#define DMA_SRC_REG		0x00
#define DMA_DST_REG		0x04
#define DMA_DIRT_REG		0x08
#define DMA_LEN_REG		0x0C
#define DOORBALL_REG		0x10

#define DMA_BUFFER_LEN		4096
#define PCI_TO_DDR		0
#define DDR_TO_PCI		1

struct BiscuitOS_pci_device {
	struct pci_dev *pdev;
	/* MMIO BAR */
	void __iomem *mmio;
	/* PHYSICAL ADDRESS FOR DMA MEMORY */
	dma_addr_t dma_addr;
	/* VIRTUAL ADDRESS FOR DMA MEMORY */
	char *dma_buffer;
};
static struct BiscuitOS_pci_device *bpdev;

static irqreturn_t BiscuitOS_msix_handler(int irq, void *dev)
{
	printk("INTERRUPT HANDLER RUNNING.\n");
	/* TODO */
	printk("DMA Buffer[%s]\n", bpdev->dma_buffer);
	printk("INTERRUPT HANDLER FINISH.\n");
	return IRQ_HANDLED;
}

static void dma_ops(u64 src, u64 dst, u64 len, u64 direct)
{
	iowrite32(src, bpdev->mmio + DMA_SRC_REG);
	iowrite32(dst, bpdev->mmio + DMA_DST_REG);
	iowrite32(len, bpdev->mmio + DMA_LEN_REG);
	iowrite32(direct, bpdev->mmio + DMA_DIRT_REG);
	/* Doorball: kick off */
	iowrite16(0x1, bpdev->mmio + DOORBALL_REG);
}

static int BiscuitOS_pci_probe(struct pci_dev *pdev, 
				const struct pci_device_id *id)
{
	struct msix_entry msix;
	int r = 0;

	/** PCIe DEVICE INITIALIZE **/

	bpdev = kzalloc(sizeof(*bpdev), GFP_KERNEL);
	if (!bpdev) {
		r = -ENOMEM;
		printk("%s ERROR: DEVICE INFO FAILED.\n", DEV_NAME);
		goto err_alloc;
	}
	bpdev->pdev = pdev;

	/* ENABLE PCIe DEVICE */
	r = pci_enable_device(pdev);
	if (r < 0) {
		printk("%s ERROR: PCIe DEVICE ENABLE FAILED.\n", DEV_NAME);
		goto err_enable_pci;
	}

	/* REQUEST IO BAR RESOURCE */
	r = pci_request_region(pdev, MMIO_BAR, "BiscuitOS MMIO");
	if (r < 0) {
		printk("%s ERROR: Request IO-BAR Failed.\n", DEV_NAME);
		goto err_request_io;
	}

	/* MAPPING MMIO FROM MMIO-BAR */
	bpdev->mmio = pci_iomap(pdev, MMIO_BAR, 
				pci_resource_len(pdev, MMIO_BAR));
	if (!bpdev->mmio) {
		r = -EBUSY;
		printk("%s ERROR: MAPPING MMIO FAILED\n", DEV_NAME);
		goto err_iomap;
	}

	/* REQUEST MSIX INTERRUPT */
	msix = (struct msix_entry) {
		.vector = 0,
		.entry  = 0, /* 1ST MSIX INTERRUPT */
	};
	r = pci_enable_msix_range(pdev, &msix, 1, 1);
	if (r != 1) { /* ONLY REQUEST 1 MSIX INTERRUPT */
		printk("%s ERROR: ENABLE MSIX FAILED.\n", DEV_NAME);
		goto err_msix;
	}
	/* REGISTER INTERRUPT */
	r = request_irq(msix.vector, BiscuitOS_msix_handler,
				IRQF_SHARED | IRQF_TRIGGER_HIGH,
						DEV_NAME, (void *)bpdev);
	if (r < 0) {
		printk("%s ERROR: REQUEST IRQ FAILED.\n", DEV_NAME);
		goto err_irq;
	}

	/* SET MASTER */
	pci_set_master(pdev);

	printk("%s SUCCESS REGISTER PCIe DEVICE.\n", DEV_NAME);

	/* ALLOC DMA MEMORY */
	bpdev->dma_buffer = dma_alloc_coherent(&pdev->dev,
				DMA_BUFFER_LEN, &bpdev->dma_addr, GFP_KERNEL);
	if (!bpdev->dma_buffer) {
		r = -ENOMEM;
		printk("%s ERROR: ALLOC DMA MEMORY FAILED.\n", DEV_NAME);
		goto err_dma;
	}

	/* DMA: PCI TO DDR -> MEMORY WRITE TLP */
	dma_ops(0x00, bpdev->dma_addr, 0x20, PCI_TO_DDR);

	return 0;

err_dma:
	free_irq(msix.vector, NULL);
err_irq:
	pci_disable_msix(pdev);
err_msix:
	pci_iounmap(pdev, bpdev->mmio);
err_iomap:
	pci_release_region(pdev, MMIO_BAR);
err_request_io:
	pci_disable_device(pdev);
err_enable_pci:
	kfree(bpdev);
	bpdev = NULL;
err_alloc:
	return r;
}

static void BiscuitOS_pci_remove(struct pci_dev *pdev)
{
	dma_free_coherent(&pdev->dev, DMA_BUFFER_LEN,
			bpdev->dma_buffer, bpdev->dma_addr);
	free_irq(pdev->irq, NULL);
	pci_disable_msix(pdev);
	pci_iounmap(pdev, bpdev->mmio);
	pci_release_region(pdev, MMIO_BAR);
	pci_disable_device(pdev);
	kfree(bpdev);
	bpdev = NULL;
}

static const struct pci_device_id BiscuitOS_pci_ids[] = {
	{ PCI_DEVICE(0x1026, 0x1991), },
};

static struct pci_driver BiscuitOS_PCIe_driver = {
	.name		= DEV_NAME,
	.id_table	= BiscuitOS_pci_ids,
	.probe		= BiscuitOS_pci_probe,
	.remove		= BiscuitOS_pci_remove,
};

static int __init BiscuitOS_init(void)
{
	return pci_register_driver(&BiscuitOS_PCIe_driver);
}

static void __exit BiscuitOS_exit(void)
{
	pci_unregister_driver(&BiscuitOS_PCIe_driver);
}

module_init(BiscuitOS_init);
module_exit(BiscuitOS_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("BiscuitOS <buddy.zhang@aliyun.com>");
MODULE_DESCRIPTION("BiscuitOS SCHEDULE SUBSYSTEM");
